{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Adding Space\n",
    "\n",
    "### The Boltzmann Wealth Model "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you want to get straight to the tutorial checkout these environment providers:<br>\n",
    "(with Google Account) [![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/projectmesa/mesa/blob/main/docs/tutorials/1_adding_space.ipynb)<br>\n",
    "(no Google Account)   [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/projectmesa/mesa/main?labpath=docs%2Ftutorials%2F1_adding_space.ipynb) (This can take 30 seconds to 5 minutes to load)\n",
    "\n",
    "*If you are running locally, please ensure you have the latest Mesa version installed.*\n",
    "\n",
    "## Tutorial Description\n",
    "\n",
    "This tutorial extends the Boltzmann wealth model from the [Running Your First Model tutorial](https://mesa.readthedocs.io/latest/tutorials/0_first_model.html), by adding Mesa's discrete space module. \n",
    "\n",
    "In this portion, `MoneyAgent`s will move in a two dimensional grid, made up of discrete cells and randomly exchange money with other agents. \n",
    "\n",
    "*If you are starting here please see the [Running Your First Model tutorial](https://mesa.readthedocs.io/latest/tutorials/0_first_model.html) for dependency and start-up instructions*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### IN COLAB? - Run the next cell "
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {
    "vscode": {
     "languageId": "raw"
    }
   },
   "source": [
    "%pip install --quiet mesa[rec]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Import Dependencies\n",
    "This includes importing of dependencies needed for the tutorial."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [],
   "source": [
    "# Has multi-dimensional arrays and matrices.\n",
    "# Has a large collection of mathematical functions to operate on these arrays.\n",
    "import numpy as np\n",
    "\n",
    "# Data manipulation and analysis.\n",
    "import pandas as pd\n",
    "\n",
    "# Data visualization tools.\n",
    "import seaborn as sns\n",
    "\n",
    "import mesa"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Base Model\n",
    "\n",
    "The below provides the base model from which we will add our space functionality. \n",
    "\n",
    "This is from the [Running Your First Model tutorial](https://mesa.readthedocs.io/latest/tutorials/0_first_model.html) tutorial. If you have any questions about it functionality please review that tutorial."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class MoneyAgent(mesa.Agent):\n",
    "    \"\"\"An agent with fixed initial wealth.\"\"\"\n",
    "\n",
    "    def __init__(self, model):\n",
    "        # Pass the parameters to the parent class.\n",
    "        super().__init__(model)\n",
    "\n",
    "        # Create the agent's variable and set the initial values.\n",
    "        self.wealth = 1\n",
    "\n",
    "    def exchange(self):\n",
    "        # Verify agent has some wealth\n",
    "        if self.wealth > 0:\n",
    "            other_agent = self.random.choice(self.model.agents)\n",
    "            if other_agent is not None:\n",
    "                other_agent.wealth += 1\n",
    "                self.wealth -= 1\n",
    "\n",
    "\n",
    "class MoneyModel(mesa.Model):\n",
    "    \"\"\"A model with some number of agents.\"\"\"\n",
    "\n",
    "    def __init__(self, n):\n",
    "        super().__init__()\n",
    "        self.num_agents = n\n",
    "\n",
    "        # Create agents\n",
    "        MoneyAgent.create_agents(model=self, n=n)\n",
    "\n",
    "    def step(self):\n",
    "        \"\"\"Advance the model by one step.\"\"\"\n",
    "        # This function psuedo-randomly reorders the list of agent objects and\n",
    "        # then iterates through calling the function passed in as the parameter\n",
    "        self.agents.shuffle_do(\"exchange\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Execute the model\n",
    "model = MoneyModel(10)\n",
    "model.step()\n",
    "# Make sure it worked\n",
    "print(f\"You have {len(model.agents)} agents.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Adding space\n",
    "\n",
    "**Background:** Due to the complex dynamics of space and movement, Mesa offers a wide range space options and has built a structure to allow for the addition of even more spaces or custom user space creation. (Please contribute to Mesa if you develop a new space that can add to user options.)\n",
    "\n",
    "The two main approaches to space are discrete space (think cells or nodes that agents occupy) and continuous space (agents can occupy any location(s) in a three-dimensional space). Continuous space is still experimental as we continue to develop it.\n",
    "\n",
    "**Overview of Discrete Space:** For this tutorial we will be using discrete space in the classic cartesian coordinated system. As indicated in the diagram discrete space is made up of two modules. Cells and Cell Agents. \n",
    "\n",
    "**Cells:**\n",
    "The cell class represents a location that can:\n",
    "- Have properties (like temperature or resources)\n",
    "- Track and limit the agents it contains\n",
    "- Connect to neighboring cells\n",
    "- Provide neighborhood information\n",
    "\n",
    "Cells form the foundation of the cell space system, enabling rich spatial environments where both location properties and agent behaviors matter. They're useful for modeling things like varying terrain, infrastructure capacity, or environmental conditions.\n",
    "\n",
    "**Cell Agents:**\n",
    "Agents that understand how to exist in and move through cell spaces.\n",
    "\n",
    "Cell Agents are specialized agent classes that handle cell occupation, movement, and proper registration:\n",
    "- `CellAgent`: Mobile agents that can move between cells\n",
    "- `FixedAgent`: Immobile agents permanently fixed to cells\n",
    "- `Grid2DMovingAgent`: Agents with grid-specific movement capabilities\n",
    "\n",
    "These classes ensure consistent agent-cell relationships and proper state management as agents move through the space. They can be used directly or as examples for creating custom cell-aware agents.\n",
    "\n",
    "From these basic building blocks we can then add features to allow for different types of spaces and behaviors. To keep this tutorial concise we will not go through all of them, however, the current layout of discrete space is below as well as the different support modules. To find out more about the other options and what they can do, check out the [Discrete Space API](https://mesa.readthedocs.io/latest/apis/discrete_space.html)\n",
    "\n",
    "<br><br>\n",
    "![Discerete Space Diagram](../images/Discrete_Space.drawio.png)\n",
    "<br>\n",
    "\n",
    "*A big thanks to maintainer qualquel and his creation of this exceptional space dynamic.*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "**Model-specific information:**  In addition to using discrete space, the agents will access their [Moore neighborhood](https://en.wikipedia.org/wiki/Moore_neighborhood). A Moore neighborhood means agents can interact with 8 neighbors.  Instead of giving their unit of money to any random agent, they'll give it to an agent on the same cell. For the Money model multiple agents can be in the same spaces and since they are on a torus the agents on the left side can exchange money with agent on the right. Agents on the top can exchange with agents on the bottom."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Code Implementation\n",
    "\n",
    "To ensure we give our agents discrete space functionality we now instantiate our `MoneyAgent`s as `CellAgent`s. `Cell Agent` is a subclass to Mesa's `Agent` class that is specifically built to interact and move within the `discrete space` module.  \n",
    "\n",
    "Below highlights each of the changes to the base code to add space and movement of agents. \n",
    "\n",
    "**Imports**<br>\n",
    "\\# Import Cell Agent and OrthogonalMooreGrid\n",
    "- *Description:* Import the cell agent class and a specific grid construct the OrthognalMooreGrid.\n",
    "- *API*: [CellAgent](https://mesa.readthedocs.io/latest/apis/discrete_space.html#mesa.discrete_space.__init__.CellAgent) and [OrthogonalMooreGrid](https://mesa.readthedocs.io/latest/apis/discrete_space.html#mesa.discrete_space.grid.OrthogonalMooreGrid)\n",
    "\n",
    "**MoneyAgent Class**<br>\n",
    "\\# Instantiate MoneyAgent as CellAgent\n",
    "- *Description:* `MoneyAgent` inherits `CellAgent`, a subclass of `Agent`.\n",
    "- *API*: [CellAgent](https://mesa.readthedocs.io/latest/apis/discrete_space.html#mesa.discrete_space.__init__.CellAgent)\n",
    "\n",
    "\\# Instantiate agent with location (x,y)\n",
    "- *Description:* Pass the cell object as a parameter to the agent to give the agent a location\n",
    "- *API*: N/A\n",
    "\n",
    "\\# Move function\n",
    "- *Description:* Update the agents cell through methods in Mesa's discrete_space module `neighborhood`, which defaults to radius one and `select_random_cell` which selects a random cell for the provided neighborhood \n",
    "- *API*: [neighborhood](https://mesa.readthedocs.io/latest/apis/discrete_space.html#mesa.discrete_space.__init__.Cell.neighborhood) and [select_random_cell](https://mesa.readthedocs.io/latest/apis/discrete_space.html#mesa.discrete_space.__init__.CellCollection.select_random_cell)\n",
    "\n",
    "**MoneyModel Class**<br>\n",
    "\\# Instantiate an instance of Moore neighborhood space\n",
    "- *Description:* Instantiate a OrthgonalMooreGrid as `self.grid` with passing in the parameters width and height as a tuple, torus as True, a cell capacity of 5 agents, and the models random seed to the discrete space \n",
    "- *API*: [OrthogonalMooreGrid](https://mesa.readthedocs.io/latest/apis/discrete_space.html#mesa.discrete_space.grid.OrthogonalMooreGrid)\n",
    "\n",
    "\\# Randomly select agents cell\n",
    "- *Description:* Use Python's `random.choices` and pass in all cells with discrete space all_cells properties and the number of choices `k` to assign each agent a location.  \n",
    "- *API*: [random.choices](https://docs.python.org/3/library/random.html#random.choices) and [all_cells](https://mesa.readthedocs.io/latest/apis/discrete_space.html#id1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import Cell Agent and OrthogonalMooreGrid\n",
    "from mesa.discrete_space import CellAgent, OrthogonalMooreGrid\n",
    "\n",
    "\n",
    "# Instantiate MoneyAgent as CellAgent\n",
    "class MoneyAgent(CellAgent):\n",
    "    \"\"\"An agent with fixed initial wealth.\"\"\"\n",
    "\n",
    "    def __init__(self, model, cell):\n",
    "        super().__init__(model)\n",
    "        self.cell = cell  # Instantiate agent with location (x,y)\n",
    "        self.wealth = 1\n",
    "\n",
    "    # Move Function\n",
    "    def move(self):\n",
    "        self.cell = self.cell.neighborhood.select_random_cell()\n",
    "\n",
    "    def give_money(self):\n",
    "        cellmates = [\n",
    "            a for a in self.cell.agents if a is not self\n",
    "        ]  # Get all agents in cell\n",
    "\n",
    "        if self.wealth > 0 and cellmates:\n",
    "            other_agent = self.random.choice(cellmates)\n",
    "            other_agent.wealth += 1\n",
    "            self.wealth -= 1\n",
    "\n",
    "\n",
    "class MoneyModel(mesa.Model):\n",
    "    \"\"\"A model with some number of agents.\"\"\"\n",
    "\n",
    "    def __init__(self, n, width, height, seed=None):\n",
    "        super().__init__(seed=seed)\n",
    "        self.num_agents = n\n",
    "        # Instantiate an instance of Moore neighborhood space\n",
    "        self.grid = OrthogonalMooreGrid(\n",
    "            (width, height), torus=True, capacity=10, random=self.random\n",
    "        )\n",
    "\n",
    "        # Create agents\n",
    "        agents = MoneyAgent.create_agents(\n",
    "            self,\n",
    "            self.num_agents,\n",
    "            # Randomly select agents cell\n",
    "            self.random.choices(self.grid.all_cells.cells, k=self.num_agents),\n",
    "        )\n",
    "\n",
    "    def step(self):\n",
    "        self.agents.shuffle_do(\"move\")\n",
    "        self.agents.do(\"give_money\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's create a model with 100 agents on a 10x10 grid, and run it for 20 steps."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = MoneyModel(100, 10, 10)\n",
    "for _ in range(20):\n",
    "    model.step()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's use seaborn and numpy to visualize the number of agents residing in each cell. To do that, we create a numpy array of the same size as the grid, filled with zeros. \n",
    "\n",
    "Then again use `all_cells` to loop over every cell in the grid, giving us each cell's position (cell coordinate attribute) and its contents (cell agent attribute). \n",
    "\n",
    "[Cell API](https://mesa.readthedocs.io/latest/apis/discrete_space.html#mesa.discrete_space.__init__.Cell)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "agent_counts = np.zeros((model.grid.width, model.grid.height))\n",
    "\n",
    "for cell in model.grid.all_cells:\n",
    "    agent_counts[cell.coordinate] = len(cell.agents)\n",
    "# Plot using seaborn, with a visual size of 5x5\n",
    "g = sns.heatmap(agent_counts, cmap=\"viridis\", annot=True, cbar=False, square=True)\n",
    "g.figure.set_size_inches(5, 5)\n",
    "g.set(title=\"Number of agents on each cell of the grid\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercises\n",
    "- Change the size of the grid\n",
    "- Change the capacity of the cells\n",
    "- Try a different grid space like OrthognalVonNeumann, Network, or Voronoi"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Next Steps\n",
    "\n",
    "Check out the [collecting data tutorial](https://mesa.readthedocs.io/latest/tutorials/2_collecting_data_tutorial.html) on how to collect data form your model."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "[Comer2014] Comer, Kenneth W. “Who Goes First? An Examination of the Impact of Activation on Outcome Behavior in AgentBased Models.” George Mason University, 2014. http://mars.gmu.edu/bitstream/handle/1920/9070/Comer_gmu_0883E_10539.pdf\n",
    "\n",
    "[Dragulescu2002] Drăgulescu, Adrian A., and Victor M. Yakovenko. “Statistical Mechanics of Money, Income, and Wealth: A Short Survey.” arXiv Preprint Cond-mat/0211175, 2002. http://arxiv.org/abs/cond-mat/0211175."
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.5"
  },
  "widgets": {
   "state": {},
   "version": "1.1.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
